﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Mapping.ByCode;
using Xenta.Enums;
using Xenta.Utils;

namespace Xenta.Entities
{
    /// <summary>
    /// Represents the sales item entity.
    /// </summary>
    /// <remarks><pre>
    /// The entity regarding the disclosure level.
    /// 
    /// + Entire
    /// | - Options(of the Extended level)
    ///  + Extended
    ///   | - Attributes(of the Extended level)
    ///   | - StockEntries(of the Extended level)
    ///   + Base
    ///    | - CatalogItem(of the same level)
    ///    | - Seller(of the Base level)
    ///    | - Price
    ///    | - Flags
    ///    | - AvailableFrom
    ///    | - AvailableTo
    ///    | - UpdatedOn
    ///    | - CreatedOn
    ///    + Keys
    ///     | - SKU
    ///     + Primary
    ///      | - EntityID
    /// 
    /// Note: upper level includes lower level.
    /// </pre></remarks>
    public class SalesItemEntity : AuditableEntity,
        IRelationHolder<SalesOptionEntity>,
        IRelationHolder<SalesRuleEntity>
    {
        #region Fields

        private DateTime _availableFrom;
        private DateTime _availableTo;
        private ICollection<SalesOptionEntity> _options;
        private ICollection<SalesItemAttributeEntity> _attributes;
        private ICollection<ShoppingCartItemEntity> _shopingCartItems;
        private ICollection<SalesRuleEntity> _rules;
        private ICollection<StockEntryEntity> _stockEntries;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SalesItemEntity()
        {
            _availableFrom = DateTime.UtcNow;
            _availableTo = DateTime.MaxValue;
            _options = new List<SalesOptionEntity>();
            _rules = new List<SalesRuleEntity>();
            _attributes = new List<SalesItemAttributeEntity>();
            _shopingCartItems = new List<ShoppingCartItemEntity>();
            _stockEntries = new List<StockEntryEntity>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the seller.
        /// </summary>
        public virtual SellerEntity Seller
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the catalog item.
        /// </summary>
        public virtual CatalogItemEntity CatalogItem
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the SKU.
        /// </summary>
        public virtual string SKU
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price.
        /// </summary>
        public virtual decimal Price
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public virtual SalesItemFlags Flags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the availability date 'from'.
        /// </summary>
        public virtual DateTime AvailableFrom
        {
            get
            {
                return _availableFrom;
            }
            set
            {
                _availableFrom = value;
            }
        }

        /// <summary>
        /// Gets or sets the availability date 'to'.
        /// </summary>
        public virtual DateTime AvailableTo
        {
            get
            {
                return _availableTo;
            }
            set
            {
                _availableTo = value;
            }
        }

        #endregion

        #region Collections

        /// <summary>
        /// Gets or sets the option collection.
        /// </summary>
        public virtual ICollection<SalesOptionEntity> Options
        {
            get
            {
                return _options;
            }
            set
            {
                _options = value;
            }
        }

        /// <summary>
        /// Gets or sets the collection of attributes.
        /// </summary>
        public virtual ICollection<SalesItemAttributeEntity> Attributes
        {
            get
            {
                return _attributes;
            }
            set
            {
                _attributes = value;
            }
        }

        /// <summary>
        /// Gets or sets the shopping cart item collection.
        /// </summary>
        public virtual ICollection<ShoppingCartItemEntity> ShoppingCartItems
        {
            get
            {
                return _shopingCartItems;
            }
            set
            {
                _shopingCartItems = value;
            }
        }

        /// <summary>
        /// Gets or sets the rule collection.
        /// </summary>
        public virtual ICollection<SalesRuleEntity> Rules
        {
            get
            {
                return _rules;
            }
            set
            {
                _rules = value;
            }
        }

        /// <summary>
        /// Gets or sets the stock entry collection.
        /// </summary>
        public virtual ICollection<StockEntryEntity> StockEntries
        {
            get
            {
                return _stockEntries;
            }
            set
            {
                _stockEntries = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>The attribute value.</returns>
        public virtual string Attribute(string key)
        {
            if(String.IsNullOrWhiteSpace(key))
                return String.Empty;
            var attribute = _attributes.SingleOrDefault(x => x.Key.Equals(key));
            if(attribute == null)
                return String.Empty;
            return attribute.Value;
        }

        /// <summary>
        /// Gets the attribute value as the type T.
        /// </summary>
        /// <typeparam name="T">The type to return.</typeparam>
        /// <param name="key">The attribute key.</param>
        /// <returns>The attribute value as the type T.</returns>
        public virtual T Attribute<T>(string key)
        {
            return StringHelper.ParseAs<T>(Attribute(key));
        }

        /// <summary>
        /// Maps an option to the item.
        /// </summary>
        /// <param name="child">The option to map.</param>
        public virtual void Map(SalesOptionEntity child)
        {
            if(Options.Contains(child))
                return;
            Options.Add(child);
        }

        /// <summary>
        /// Unmaps the option from the item.
        /// </summary>
        /// <param name="child">The option to unmap.</param>
        public virtual void Unmap(SalesOptionEntity child)
        {
            if(!Options.Contains(child))
                return;
            Options.Remove(child);
        }

        /// <summary>
        /// Maps a rule to the item.
        /// </summary>
        /// <param name="child">The rule to map.</param>
        public virtual void Map(SalesRuleEntity child)
        {
            if(Rules.Contains(child))
                return;
            Rules.Add(child);
        }

        /// <summary>
        /// Unmaps the rule from the item.
        /// </summary>
        /// <param name="child">The rule to unmap.</param>
        public virtual void Unmap(SalesRuleEntity child)
        {
            if(!Rules.Contains(child))
                return;
            Rules.Remove(child);
        }

        #endregion

        #region Utilities

        protected override void Disclose(IDictionary<String, Object> obj,
            DisclosureLevel level,
            IDictionary<String, String> options)
        {
            base.Disclose(obj, level, options);
            if(level >= DisclosureLevel.Keys)
                obj["SKU"] = SKU;
            if(level >= DisclosureLevel.Base)
            {
                obj["CatalogItem"] = CatalogItem.Disclose(level, options);
                obj["Seller"] = Seller.Disclose(DisclosureLevel.Base, options);
                obj["Price"] = Price;
                obj["Flags"] = Flags.ToString();
                obj["AvailableFrom"] = AvailableFrom;
                obj["AvailableTo"] = AvailableTo;
            }
            if(level >= DisclosureLevel.Extended)
            {
                obj["Attributes"] = Attributes.Map(x => x.Disclose(DisclosureLevel.Extended, options));
                obj["StockEntries"] = StockEntries.Map(x => x.Disclose(DisclosureLevel.Extended, options));
            }
            if(level >= DisclosureLevel.Entire)
            {
                obj["Options"] = Options.Map(x => x.Disclose(DisclosureLevel.Extended, options));
                obj["Rules"] = Rules.Map(x => x.Disclose(DisclosureLevel.Extended, options));
            }
        }

        #endregion
    }

    /// <summary>
    /// The entity NHibernate mapping.
    /// </summary>
    public class SalesItemMapping : EntityMapping<SalesItemEntity>
    {
        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SalesItemMapping()
        {
            Table("fwk_SalesItems");
            Id(x => x.EntityID, m =>
            {
                m.Column("ItemID");
                m.Generator(Generators.Identity);
            });
            ManyToOne(x => x.Seller, m =>
            {
                m.Column("SellerID");
                m.ForeignKey("FK_fwk_SalesItems_fwk_Sellers");
                m.NotNullable(true);
            });
            ManyToOne(x => x.CatalogItem, m =>
            {
                m.Column("CatalogItemID");
                m.ForeignKey("FK_fwk_SalesItems_fwk_CatalogItems");
                m.NotNullable(true);
                m.Fetch(FetchKind.Join);
            });
            Property(x => x.SKU, m =>
            {
                m.NotNullable(true);
                m.Unique(true);
                m.Index("IX_fwk_SalesItems");
                m.Length(100);
                m.Column(c =>
                {
                    c.SqlType("nvarchar(100)");
                });
            });
            Property(x => x.Price, m =>
            {
                m.Precision(19);
                m.Scale(4);
                m.NotNullable(true);
            });
            Property(x => x.Flags, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.AvailableFrom, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.AvailableTo, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.CreatedOn, m =>
            {
                m.NotNullable(true);
            });
            Property(x => x.UpdatedOn, m =>
            {
                m.NotNullable(true);
            });
            Bag(x => x.Options, m =>
            {
                m.Table("fwk_MSalesItemOptions");
                m.Key(k =>
                {
                    k.Column("ItemID");
                    k.ForeignKey("FK_fwk_MSalesItemOptions_fwk_SalesItems");
                    k.NotNullable(true);
                });
            },
            a =>
            {
                a.ManyToMany(m =>
                {
                    m.Column("OptionID");
                    m.ForeignKey("FK_fwk_MSalesItemOptions_fwk_SalesOptions");
                });
            });
            Bag(x => x.Rules, m =>
            {
                m.Table("fwk_MSalesItemRules");
                m.Key(k =>
                {
                    k.Column("ItemID");
                    k.ForeignKey("FK_fwk_MSalesItemRules_fwk_SalesItems");
                    k.NotNullable(true);
                });
            },
            a =>
            {
                a.ManyToMany(m =>
                {
                    m.Column("RuleID");
                    m.ForeignKey("FK_fwk_MSalesItemRules_fwk_SalesRules");
                });
            });
            Bag(x => x.Attributes, m =>
            {
                m.Table("fwk_GenericAttributes");
                m.Key(k =>
                {
                    k.Column("HolderID");
                    k.NotNullable(true);
                });
                m.Inverse(true);
                m.BatchSize(10);
            },
            a =>
            {
                a.OneToMany();
            });
            Bag(x => x.StockEntries, m =>
            {
                m.Table("fwk_StockEntries");
                m.Key(k =>
                {
                    k.Column("ItemID");
                    k.ForeignKey("FK_fwk_StockEntries_fwk_SalesItems");
                    k.NotNullable(true);
                });
                m.Inverse(true);
            },
            a =>
            {
                a.OneToMany();
            });
            Bag(x => x.ShoppingCartItems, m =>
            {
                m.Table("fwk_ShoppingCartItems");
                m.Key(k =>
                {
                    k.Column("SalesItemID");
                    k.ForeignKey("FK_fwk_ShoppingCartItems_fwk_SalesItems");
                    k.NotNullable(true);
                });
                m.Inverse(true);
                m.BatchSize(10);
            },
            a =>
            {
                a.OneToMany();
            });
        }
    }
}
